#pragma once
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
#include <memory>
#include <sstream>
#include "bundle.h"

namespace cloud
{
    enum
    {
        STAT_ERR = 21,
        RFIND_ERR,
        OPEN_ERR,
        WRITE_ERR,
        READ_ERR,
    };
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename)
        :_filename(filename)
        {}
        int64_t FileSize()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "stat error" << std::endl;
                exit(STAT_ERR);
            }
            return st.st_size;
        }
        time_t LastMTime()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "stat error" << std::endl;
                exit(STAT_ERR);
            }
            return st.st_mtim.tv_sec;
        }
        time_t LastATime()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "stat error" << std::endl;
                exit(STAT_ERR);
            }
            return st.st_atim.tv_sec;
        }
        std::string FileName()
        {
            size_t pos = _filename.rfind('/');
            if(pos == std::string::npos)
            {
                std::cout << "filename rfind error" << std::endl;
                exit(RFIND_ERR);
            }
            std::string filename = _filename.substr(pos + 1);
            return filename;
        }
        bool SetContent(std::string& body)
        {
            std::ofstream ofs;
            ofs.open(_filename, std::ios::binary);
            if(ofs.is_open() == false)
            {
                std::cout << "open error" << std::endl;
                exit(OPEN_ERR);
            }
            ofs.write(&body[0], body.size());
            if(ofs.good() == false)
            {
                std::cout << "write error" << std::endl;
                ofs.close();
                exit(WRITE_ERR);
            }
            ofs.close();
            return true;
        }
        bool GetContent(std::string *body)
        {
            size_t fsize = this->FileSize();
            return GetPosLen(body, 0, fsize);
        }
        bool GetPosLen(std::string *body, size_t pos, size_t len)
        {
            std::ifstream ifs;
            ifs.open(_filename, std::ios::binary);
            if(ifs.is_open() == false)
            {
                std::cout << "open error" << std::endl;
                exit(OPEN_ERR);
            }
            size_t fsize = this->FileSize();
            if(pos + len > fsize)
            {
                std::cout << "get file len is error\n";
                return false;
            }
            ifs.seekg(pos, std::ios::beg);
            body->resize(len);
            ifs.read(&(*body)[0], len);
            if(ifs.good() == false)
            {
                std::cout << "read error\n";
                ifs.close();
                exit(READ_ERR);
            }
            ifs.close();
            return true;
        }
        bool Compress(const std::string &packname)
        {
            std::string body;
            this->GetContent(&body);
            std::string packed = bundle::pack(bundle::LZIP, body);
            FileUtil fu(packname);
            fu.SetContent(packed);
            return true;
        }
        bool UnCompress(const std::string& unpackname)
        {
            std::string body;
            this->GetContent(&body);
            std::string unpacked = bundle::unpack(body);
            FileUtil fu(unpackname);
            fu.SetContent(unpacked);
            return true;
        }
        bool Exists()
        {
            return fs::exists(_filename);
        }
        bool CreateDirectory()
        {
            if(this->Exists() == true) return true;
            return fs::create_directories(_filename);
        }
        bool GetDirectory(std::vector<std::string>* arry)
        {
            for(auto& e : fs::directory_iterator(_filename))
            {
                if(fs::is_directory(e) == true) continue;
                arry->push_back(fs::path(e).relative_path().string());
            }
            return true;
        }
        ~FileUtil()
        {}
    private:
        std::string _filename;
    };
    class JsonUtil
    {
    public:
        static bool Serialize(const Json::Value& root, std::string* str)
        {
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            std::stringstream ss;
            sw->write(root, &ss);
            *str = ss.str();
            return true;
        }
        static bool UnSerialize(const std::string& str, Json::Value* root)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
            std::string err;
            if(cr->parse(str.c_str(), str.c_str()+str.size(), root, &err))
            {
                std::cout << "parse error " << err << std::endl;
                return false;
            }
            return true;
        }
    };
}